import motor.motor_asyncio
from bson.objectid import ObjectId
from passlib.context import CryptContext
from cryptography.fernet import Fernet


MONGO_DETAILS = "mongodb://mongo-db:27017"

client = motor.motor_asyncio.AsyncIOMotorClient(MONGO_DETAILS)
database = client.parolus

paroluses_collection = database.get_collection("paroluses_collection")
users_collection = database.get_collection("users_collection")
app_collection = database.get_collection("app_collection")

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def key_helper(user) -> dict:
    return {
        "id": str(user["_id"]),
        "key": user["key"],
        "recievingShares": user["recievingShares"]
    }


def user_helper(user) -> dict:
    return {
        "id": str(user["_id"]),
        "username": user["username"],
        "password": user["password"],
        "recievingShares": user["recievingShares"]
    }


def recieving_helper(user) -> dict:
    return {
        "id": str(user["_id"]),
        "username": user["username"],
        "recievingShares": user["recievingShares"]
    }


def parolus_helper(parolus) -> dict:
    return {
        "id": str(parolus["_id"]),
        "title": parolus["title"],
        "login": parolus["login"],
        "password": parolus["password"],
        "user_id": parolus["user_id"],
        "shared": parolus["shared"]
    }


def config_helper(config) -> dict:
    return {
        "enabled_registration": config["enabled_registration"]
    }


async def add_user(user_data: dict) -> dict:
    existing_user = await users_collection.find_one({"username": user_data['username']})
    if existing_user:
        raise ValueError("Username already exists")
    hashed_password = pwd_context.hash(user_data['password'])
    user_data['password'] = hashed_password
    user = await users_collection.insert_one(user_data)
    new_user = await users_collection.find_one({"_id": user.inserted_id})
    return user_helper(new_user)


async def update_user_password(user_id: str, new_password: str):
    hashed_password = pwd_context.hash(new_password)
    await users_collection.update_one(
        {"_id": ObjectId(user_id)},
        {"$set": {"password": hashed_password}}
    )
    return True


async def update_user_status(user_id: str, new_status: bool):
    await users_collection.update_one(
        {"_id": ObjectId(user_id)},
        {"$set": {"recievingShares": new_status}}
    )
    return True


async def retrieve_users():
    users = []
    async for user in users_collection.find():
        users.append(user_helper(user))
    return users


async def retrieve_user(id: str) -> dict:
    user = await users_collection.find_one({"_id": ObjectId(id)})
    if user:
        return user_helper(user)


async def retrieve_user_full(id: str) -> dict:
    user = await users_collection.find_one({"_id": ObjectId(id)})
    if user:
        return key_helper(user)


async def get_user(username: str):
    return await users_collection.find_one({"username": username})


async def retrieve_parolus(id: str) -> dict:
    parolus = await paroluses_collection.find_one({"_id": ObjectId(id)})
    if parolus:
        return parolus_helper(parolus)


async def add_parolus(parolus_data: dict, hash_key: str) -> dict:
    cipher_suite = Fernet(hash_key)
    encrypted_title = cipher_suite.encrypt(parolus_data['title'].encode())
    encrypted_login = cipher_suite.encrypt(parolus_data['login'].encode())
    encrypted_password = cipher_suite.encrypt(parolus_data['password'].encode())
    parolus_data['title'] = encrypted_title.decode()
    parolus_data['login'] = encrypted_login.decode()
    parolus_data['password'] = encrypted_password.decode()

    parolus = await paroluses_collection.insert_one(parolus_data)
    new_parolus = await paroluses_collection.find_one({"_id": parolus.inserted_id})
    return parolus_helper(new_parolus)


async def retrieve_paroluses(user_id: str, hash_key: str):
    paroluses = []
    async for parolus in paroluses_collection.find({"user_id": user_id}):
        cipher_suite = Fernet(hash_key)
        decrypted_title = cipher_suite.decrypt(parolus['title'].encode()).decode()
        decrypted_login = cipher_suite.decrypt(parolus['login'].encode()).decode()
        decrypted_password = cipher_suite.decrypt(parolus['password'].encode()).decode()
        parolus['title'] = decrypted_title
        parolus['login'] = decrypted_login
        parolus['password'] = decrypted_password
        paroluses.append(parolus_helper(parolus))
    return paroluses


async def update_parolus(id: str, data: dict, hash_key: str):
    if len(data) < 1:
        return False

    cipher_suite = Fernet(hash_key)
    encrypted_title = cipher_suite.encrypt(data['title'].encode())
    encrypted_login = cipher_suite.encrypt(data['login'].encode())
    encrypted_password = cipher_suite.encrypt(data['password'].encode())
    data['title'] = encrypted_title.decode()
    data['login'] = encrypted_login.decode()
    data['password'] = encrypted_password.decode()

    parolus = await paroluses_collection.find_one({"_id": ObjectId(id)})
    if parolus:
        updated_parolus = await paroluses_collection.update_one(
            {"_id": ObjectId(id)}, {"$set": data}
        )
        if updated_parolus:
            return True
        return False


async def delete_parolus(id: str):
    parolus = await paroluses_collection.find_one({"_id": ObjectId(id)})
    if parolus:
        await paroluses_collection.delete_one({"_id": ObjectId(id)})
        return True


async def delete_user(user_id: str):
    user = await users_collection.find_one({"_id": ObjectId(user_id)})
    if user:
        await users_collection.delete_one({"_id": ObjectId(user_id)})
        return True
    return False


async def delete_all_paroluses(user_id: str):
    await paroluses_collection.delete_many({"user_id": user_id})


async def check_app_config() -> bool:
    config = await app_collection.find_one({'primary': True})
    if config:
        return config_helper(config)
    else:
        await app_collection.insert_one({'primary': True, 'enabled_registration': False})
        return True


async def change_app_config(new_config) -> bool:
    config = await app_collection.update_one({'primary': True}, {"$set": {"enabled_registration": new_config}})
    if config:
        return True
    else:
        return False
